/* CSc533 homework 6 - Surface Revolution
 * Ian Ryan & Andrew Wilt 
 */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>

#include <GL/glew.h>
#include <GL/glu.h>
#include <GL/glut.h>

#include "defs.h"
#include "hw6.h"
#include "bump.h"

int debug = 0;

static void display_CB(void);
static void idle_CB(void);
static void key_CB(unsigned char key, int x, int y);
static void arrow_CB(int key, int x, int y);

int width = WIDTH;
int height = HEIGHT;
int view0w = WIDTH; // Mobile viewwindow width
int view0h = HEIGHT - PVIEWH; // Mobile viewwindow height

float light[3] = { CAM_X, CAM_Y, CAM_Z / 2.0 };
float ambient[4] = { 0.0, 0.0, 1.0, 1.0 };
float diffuse[4] = { 0.0, 1.0, 0.0, 1.0 };
float specular[4] = { 1.0, 0.0, 0.0, 1.0 };

float camera[3] = { CAM_X, CAM_Y, CAM_Z }; // starting camera position
float look[3] = { 0, 0, 1 }; // look vector from camera
static float look_angle = 45.;
static char assignment_name[] = "Hw6 - Surfaces of Revolution";

bool use_shader = true; // Don't use the shader

float THETA = 0;
int rotate_animation = 0;

VERTEX controlline[NUM_CNTL_POINTS]; // X,Y,Z values for the control points of the bezier curve
VERTEX bezier[MAX_POINTS];
int selected_point = 0; // first point is selected
int active_point = 0;
int recalc_normals = 0;

GLuint shaderProg[2];

void printLog(GLuint obj) {
	int infologLength = 0;
	GLint maxLength;

	if (glIsShader(obj))
		glGetShaderiv(obj, GL_INFO_LOG_LENGTH, &maxLength);
	else
		glGetProgramiv(obj, GL_INFO_LOG_LENGTH, &maxLength);

	char *infoLog = new char[maxLength];

	if (glIsShader(obj))
		glGetShaderInfoLog(obj, maxLength, &infologLength, infoLog);
	else
		glGetProgramInfoLog(obj, maxLength, &infologLength, infoLog);

	if (infologLength > 0)
		printf("%s\n", infoLog);
}

char* readShaderSource(const char* shaderFile) {
	struct stat statBuf;
	FILE* fp = fopen(shaderFile, "r");
	char* buf;

	fstat(fileno(fp), &statBuf);
	buf = (char*) malloc((statBuf.st_size + 1) * sizeof(char));
	memset(buf, '\0', statBuf.st_size + 1);

	fread(buf, 1, (size_t) statBuf.st_size, fp);
	fclose(fp);

	return buf;
}

void build_shader(GLuint shaderHandle, const char *vertShaderFile,
		const char *fragShaderFile) {
	GLuint vertHandle;
	GLuint fragHandle;

	const GLchar *vertSource, *fragSource;

	/* load, compile, and attach the vertex shader */
	vertSource = readShaderSource(vertShaderFile);
	vertHandle = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertHandle, 1, &vertSource, NULL);
	glCompileShader(vertHandle);
	glAttachShader(shaderHandle, vertHandle);

	fprintf(stdout, "Vertex Shader code:\n%s\n", vertSource);
	printLog(vertHandle);

	/* load, compile, and attach the fragment shader */
	fragSource = readShaderSource(fragShaderFile);
	fragHandle = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragHandle, 1, &fragSource, NULL);
	glCompileShader(fragHandle);
	glAttachShader(shaderHandle, fragHandle);

	fprintf(stdout, "Fragment Shader code:\n%s\n", fragSource);
	printLog(fragHandle);
}

int main(int argc, char *argv[]) {

	int i, j;
	int win;

	GLenum glewErr;

	/* seed the random number generator */
	srand( time(NULL));

	/* opengl setup */
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
	glutInitWindowSize(width, height);
	win = glutCreateWindow(assignment_name);
	glutDisplayFunc(display_CB);
	glutIdleFunc(idle_CB);
	glutKeyboardFunc(key_CB);
	glutSpecialFunc(arrow_CB);
	glClearColor((GLclampf) 0.0, (GLclampf) 0.0, (GLclampf) 0.0, (GLclampf) 0.0);
	gluOrtho2D(0, WIDTH, HEIGHT, 0);

	/* clearing depth is everything less than 1 */
	glClearDepth(1.0);
	glEnable ( GL_DEPTH_TEST);
	glDepthFunc ( GL_LEQUAL);

	glEnable ( GL_CULL_FACE);
	glCullFace ( GL_BACK);

	glewErr = glewInit();
	if (glewErr != GLEW_OK) {

		/* Problem: glewInit failed, something is seriously wrong. */
		fprintf(stderr, "Error: %s\n", glewGetErrorString(glewErr));
	}

	fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));

	if (!GLEW_VERSION_2_0) {
		fprintf(stdout, "Error: Required extensions not supported.\n");
		fprintf(stdout, "  Program requires OpenGL version 2.0 or later.\n");
		exit(-1);
	}

	/* initialize the control points of the Bezier curve */
	controlline[0].c[X] = X_OFFSET + 50;
	controlline[0].c[Y] = Y_OFFSET;
	controlline[0].c[Z] = 0;

	controlline[1].c[X] = X_OFFSET + 100;
	controlline[1].c[Y] = Y_OFFSET + 35;
	controlline[1].c[Z] = 0;

	controlline[2].c[X] = X_OFFSET + 320;
	controlline[2].c[Y] = Y_OFFSET - 45;
	controlline[2].c[Z] = 0;

	controlline[3].c[X] = X_OFFSET + 350;
	controlline[3].c[Y] = Y_OFFSET + 45;
	controlline[3].c[Z] = 0;

	/* shader stuff */
	//	shaderProg [ 0 ] = glCreateProgram ();
	//	build_shader ( shaderProg [ 0 ], "vertex.glsl", "fragment.glsl" );
	//	glLinkProgram ( shaderProg [ 0 ] );
	shaderProg[1] = glCreateProgram();
	build_shader(shaderProg[1], "bezier-vertex.glsl", "bezier-fragment.glsl");
	glLinkProgram(shaderProg[1]);

	calc_bezier_curve();

	glutMainLoop();

	return EXIT_FAILURE;
}

static void display_CB(void) {
	int i, j, m, n;
	float tex_specular[] = { 1.0, 1.0, 1.0, 1.0 };
	float angle, nextangle;

	// Clear the buffer from previous rendering
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	/* Draw the mobile in the mobile view */
	glMatrixMode ( GL_PROJECTION);
	glLoadIdentity();
	glViewport(0, PVIEWH, width, height);

	glUseProgram(shaderProg[1]);
	gluPerspective(look_angle, 1, FRONT_CLIP, BACK_CLIP);
	glMatrixMode ( GL_MODELVIEW);
	glLoadIdentity();

	gluLookAt(camera[X], camera[Y], camera[Z], camera[X] + look[X]
			* lens_distance, camera[Y] + look[Y] * lens_distance, camera[Z]
			+ look[Z] * lens_distance, 0, 1, 0);

	// Set the specular value of the plates so that the fragment shader can retrieve it
	glMaterialfv(GL_FRONT, GL_SPECULAR, tex_specular);
	glMaterialf(GL_FRONT, GL_SHININESS, 100.0); // plate is max shiny

	/* shader vars */
	GLint b_PosHandle = glGetAttribLocation(shaderProg[1], "bezier_Pos");
	GLfloat b_Pos[3];

	GLint slice_OriginHandle = glGetAttribLocation(shaderProg[1],
			"slice_Origin");
	GLfloat s_Orig[3];

	/* for each side create a quad strip */
	for (i = 0; i < NUM_ROTATE; i++) {

		glColor3f((float) i / NUM_ROTATE, 0.0, (float) (NUM_ROTATE - i)
				/ NUM_ROTATE);

		angle = i * ROT_ANGLE + THETA;
		nextangle = (i + 1) * ROT_ANGLE + THETA;

		s_Orig[X] = 0;
		s_Orig[Z] = 0;

		glBegin( GL_QUAD_STRIP);
		for (j = 0; j < NUM_SIDES; j++) {
			s_Orig[Y] = j * 5;
			b_Pos[X] = bezier[j].c[X];
			b_Pos[Y] = bezier[j].c[Y];
			b_Pos[Z] = bezier[j].c[Z];
			glVertexAttrib3fv(b_PosHandle, b_Pos);
			glVertex3f(sin(angle) * RADIUS, j * 5., cos(angle) * RADIUS);
			glVertexAttrib3fv(b_PosHandle, b_Pos);
			glVertex3f(sin(nextangle) * RADIUS, j * 5., cos(nextangle) * RADIUS);
		}
		glEnd();
	}

	// Set up a viewport for the poly line view
	glViewport(0, 0, width, PVIEWH);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0, width, 0, PVIEWH);

	glUseProgram(0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glColor3f(1.0, 1.0, 1.0);
	draw_curve();

	glutSwapBuffers();

	return;
}

static void idle_CB(void) {

	if (rotate_animation)
		THETA += ROT_ANGLE;

	glutPostRedisplay();
}

static void key_CB(unsigned char key, int x, int y) {
	int old_max;
	int i;

	if (debug)
		printf("key - %c\n", key);

	switch (key) {

	case 'h':
		printf("option keys enabled:\n");
		printf("h\tshow options\n");
		printf("Arrow Keys\tMove the selected control point\n");
		printf("<>\tSelect a control point to the left or right\n");
		printf("esc\tResets all the Bezier control points\n");
		printf("d\tToggles debugging statements on and off\n");
		printf("q\tToggles shader program on and off\n");

	case 'r':
	case 'R':
		rotate_animation = !rotate_animation;
		break;

	case '<':
		/* Make the selected control point one to the left */
		selected_point = (selected_point - 1) % NUM_CNTL_POINTS;
		break;

	case '>':
		/* Make the selected control point one to the left*/
		selected_point = (selected_point + 1) % NUM_CNTL_POINTS;
		break;

	case 27:
		/* ESC key reset all control points of Bezier curve to original values */
		for (i = 0; i < NUM_CNTL_POINTS; i++) {

			controlline[i].c[X] = 0;
			controlline[i].c[Y] = 0;
			controlline[i].c[Z] = 0;
		}
		break;

	case 'd':
		/* toggle debugging on and off */
		debug = !debug;
		if (debug)
			printf("debugging turned on\n");
		break;

	case 'q':
	case 'Q':
		/* Quit the program */
		exit ( EXIT_SUCCESS);
		break;
	}

	if (debug) {
		printf("camera - { %f, %f, %f }\n", camera[X], camera[Y], camera[Z]);
		printf("look - { %f, %f, %f }\n", look[X], look[Y], look[Z]);
		printf("theta - %f\n", THETA);
	}

	glutPostRedisplay();
}

void arrow_CB(int key, int x, int y) {

	switch (key) {

	case GLUT_KEY_LEFT:
		/* Move the currently selected control point to the left */
		controlline[selected_point].c[X]--;
		if (debug) {
			printf("New value for point %d (%f,%f,%f)\n", selected_point,
					controlline[selected_point].c[X],
					controlline[selected_point].c[Y],
					controlline[selected_point].c[Z]);
		}
		break;

	case GLUT_KEY_RIGHT:
		/* Move the currently selected control point to the right */
		controlline[selected_point].c[X]++;
		if (debug) {
			printf("New value for point %d (%f,%f,%f)\n", selected_point,
					controlline[selected_point].c[X],
					controlline[selected_point].c[Y],
					controlline[selected_point].c[Z]);
		}
		break;

	case GLUT_KEY_UP:
		/* Move the currently selected control point up */
		controlline[selected_point].c[Y]++;
		if (debug) {
			printf("New value for point %d (%f,%f,%f)\n", selected_point,
					controlline[selected_point].c[X],
					controlline[selected_point].c[Y],
					controlline[selected_point].c[Z]);
		}
		break;

	case GLUT_KEY_DOWN:
		/* Move the currently selected control point down */
		controlline[selected_point].c[Y]--;
		if (debug) {
			printf("New value for point %d (%f,%f,%f)\n", selected_point,
					controlline[selected_point].c[X],
					controlline[selected_point].c[Y],
					controlline[selected_point].c[Z]);
		}
		break;

	}

	calc_bezier_curve();
}

/* find the unit vector for the segment between two points */
void find_unitVec(float* start, float* end, float* retval) {
	float length;
	float dx, dy, slope;

	dx = end[X] - start[X];
	dy = end[Y] - start[Y];

	if (dx == 0) {
		retval[X] = 1;
		retval[Y] = 0;
		retval[Z] = 0;
	}

	else {
		length = findDist(start, end);
		retval[X] = dx / length;
		retval[Y] = dy / length;
		retval[Z] = 0;
	}
}

/* find the distance from one point to another point */
float findDist(float* start, float* end) {
	float dx, dy, dz;
	float length;

	dx = end[X] - start[X];
	dy = end[Y] - start[Y];
	dz = end[Z] - start[Z];

	return sqrt(dx * dx + dy * dy + dz * dz);
}
